
"""
libpcap is an open format for both the application Ethereal and its successuor
wireshark. More information about the format could refer to the web page:

<a href="http://wiki.wireshark.org/Development/LibpcapFileFormat">
http://wiki.wireshark.org/Development/LibpcapFileFormat</a>

The generator obeys the specification, keeps a formatter for the format
recognition to build up the tree structure, and contains a generator to create
a libpcap file with default HTTP header.

In TCP/IP specification, a package couldn't be more than 65535, thus, the size
of a package couldn't be set over the limiation. And the HTTP header could be
set to empty and the package would be only following the Ethernet II
specification.
"""

import wx
import re
import time

__version__ = '1.13'
__aof__ = ('Libpcap', 'CAP')
__extension__ = (('Libpcap Files', ('*.pcap', '*.cap')),)

_SOURCE_IP_ADDR = 'Pcap Source IP Address'
_DEST_IP_ADDR = 'Pcap Destination IP Address'
_HTTP_HEADER = 'HTTP/1.1 200 OK\nData: %DATE%\nContent-Type: \
@MIME_TYPE@\nContent-length: @PACKAGE_SIZE@\nProxy-Connection: \
Keep-Alive\nConnection: Keep-Alive\n\n'

__configs__ = (
    {'name': _SOURCE_IP_ADDR,
     'attr': 'str',
     'default': '1.1.1.1'},
    {'name': _DEST_IP_ADDR,
     'attr': 'str',
     'default': '2.2.2.2'}
)

_resource = '''
<?xml version="1.0"?>
<resource>
    <object class="wxPanel" name="panel_1">
        <style>wxTAB_TRAVERSAL</style>
        <object class="wxFlexGridSizer" name="grid_sizer_1">
            <rows>3</rows>
            <cols>2</cols>
            <vgap>5</vgap>
            <hgap>5</hgap>
            <object class="sizeritem">
                <border>5</border>
                <object class="wxStaticText" name="label_1">
                    <attribute>1</attribute>
                    <label>Package Size</label>
                </object>
            </object>
            <object class="sizeritem">
                <option>1</option>
                <flag>wxALL|wxEXPAND</flag>
                <border>5</border>
                <object class="wxTextCtrl" name="package_size">
                    <size>300,-1</size>
                    <value>64000</value>
                </object>
            </object>
            <object class="sizeritem">
                <border>5</border>
                <object class="wxStaticText" name="label_2">
                    <attribute>1</attribute>
                    <label>MIME Type</label>
                </object>
            </object>
            <object class="sizeritem">
                <option>1</option>
                <flag>wxALL|wxEXPAND</flag>
                <border>5</border>
                <object class="wxComboBox" name="mime_type">
                    <size>300,-1</size>
                    <value>application/vnd.wap.mms-message</value>
                    <content></content>
                </object>
            </object>
            <object class="sizeritem">
                <border>5</border>
                <object class="wxStaticText" name="label_3">
                    <attribute>1</attribute>
                    <label>HTTP Header</label>
                </object>
            </object>
            <object class="sizeritem">
                <option>1</option>
                <flag>wxALL|wxEXPAND</flag>
                <border>5</border>
                <object class="wxTextCtrl" name="http_header">
                    <size>300,-1</size>
                    <style>wxTE_MULTILINE|wxHSCROLL</style>
                    <value>%s</value>
                </object>
            </object>
        </object>
    </object>
</resource>
''' % _HTTP_HEADER


class _Libpcap:  # pylint: disable=W0232
    def Execute(self, source, target, notify, **kws):
        plxtype = kws.get('plxtype')
        if plxtype == 'formatter':
            return self.__doFormat(source, target, notify, kws)
        elif plxtype == 'generator':
            return self.__doGenerate(source, target, notify, kws)
        else:
            return False

    def __doFormat(self, source, target, notify, kws):  # pylint: disable=W0613
        length = len(source)
        entry = target.getEntry()
        _atoi = kws['_i'].atoi

        # Header
        _HEADER = (
            ('Magic', 4, 'value,export'),
            ('Major', 2, 'decimal'),
            ('Minor', 2, 'decimal'),
            ('thiszone', 4, 'value'),
            ('sigfigs', 4, 'decimal'),
            ('snaplen', 4, 'decimal'),
            ('network', 4, 'value')
        )

        kws['offset'] = 0
        target.setEndian('little-endian', kws)
        ret, objectp, consumed, _, exported = target.parse(
            'Header', _HEADER, afile=source, **kws)
        if ret:
            if exported[0] != 0xa1b2c3d4:
                target.setEndian('big-endian', kws)
                ret, objectp, consumed, _, exported = target.parse(
                    'Header', _HEADER, afile=source, **kws)
                if ret:
                    ret = exported[0] == 0xa1b2c3d4

        if ret:
            entry.append(objectp)
            kws['offset'] += consumed

        # Frames
        if ret:
            def _time(_, dat, **kws):
                return '%d.%d' % (_atoi(dat[:4], kws), _atoi(dat[4:], kws))

            nb = 1
            while ret and kws['offset'] < length:
                _FRAME = (
                    ('Time', 8, 'string', {'function': _time}),
                    ('Incl Length', 4, 'decimal,export'),
                    ('Orig Length', 4, 'decimal'),
                    ('content', "$0", 'object,eval')
                )

                ret, objectp, consumed, _, _ = target.parse(
                    'Frame %d' % nb, _FRAME, afile=source, **kws)
                if ret:
                    entry.append(objectp)
                    kws['offset'] += consumed
                    nb += 1

        return ret and len(entry) > 1

    def __doGenerate(self, source, output,
                     notify, kws):  # pylint: disable=W0613
        def _itol(number, length, order=0):
            ret = list()

            while number > 0 and length > 0:
                number, a = divmod(number, 256)
                ret.append(a)
                length -= 1

            if length > 0:
                ret.extend((0,) * length)

            if order:
                ret.reverse()

            return ret

        def _checksum(data):
            checksum = 0

            i, length = 0, len(data)
            if isinstance(data, (list, tuple)):
                while i < length:
                    checksum += data[i]
                    if i + 1 < length:
                        checksum += data[i + 1] * 256
                    i += 2
            else:
                while i < length:
                    checksum += ord(data[i])
                    if i + 1 < length:
                        checksum += ord(data[i + 1]) * 256
                    i += 2

            while checksum > 65535:
                checksum, div = divmod(checksum, 65536)
                checksum += div

            return (~checksum) & 0xffff

        def _ensureIpAddr(ipAddress, defaultAddress):
            ret = list()

            segs = ipAddress.split('.')
            if len(segs) == 4:
                for ip in segs:
                    try:
                        ret.append(int(ip))
                    except (TypeError, ValueError):
                        break
                else:
                    return ret

            return defaultAddress

        def _substitute(listp, offset, value):
            for k, v in enumerate(value):
                listp[offset + k] = v

        def _buildPcapHeader(length, psize):
            template = [
                0xd4, 0xc3, 0xb2, 0xa1, 0x02, 0x00, 0x04, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
            ]

            # the file size depends on the packet number, each packet will
            # add 54 extra bytes
            num, div = divmod(length, psize)
            if div:
                num += 1

            length += 70 * num
            _substitute(template, 16, _itol(length, 4, 1))
            return ''.join([chr(c) for c in template])

        def _buildPcapPacket(length):
            template = [
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ]

            _substitute(template, 8, _itol(length + 54, 4))
            _substitute(template, 12, _itol(length + 54, 4))

            return ''.join([chr(c) for c in template])

        def _buildPacketHeader(length, tseq, sourceIpAddr, destIpAddr):
            template = [
                # ethernet II (14)
                0x00, 0x19, 0xb9, 0x26, 0xd9, 0x9a, 0x00, 0x11,
                0x5c, 0x57, 0x49, 0x00, 0x08, 0x00,
                # Ip header (20)
                0x45, 0x00, 0x00, 0x00, 0x16, 0x38, 0x00, 0x00,
                0x3e, 0x06, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
                0x02, 0x02, 0x02, 0x02,
                # Tcp header (20)
                0x1f, 0x90, 0x13, 0x38, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x50, 0x18, 0xff, 0xff,
                0x00, 0x00, 0x00, 0x00
            ]

            # IP header
            # update the length in Ip header: Ip header + Tcp header = 40
            _substitute(template, 16, _itol(length + 40, 2, 1))
            # update the IP address
            _substitute(template, 26, sourceIpAddr)
            _substitute(template, 30, destIpAddr)
            # update the checksum in Ip header
            _substitute(template, 24, _itol(_checksum(template[14:34]), 2))

            # TCP header
            # update the checksum in Tcp header
            pseudo = template[24:32] + [0x00, 0x06] + _itol(length + 40 + 2, 1)
            _substitute(
                template, 50,
                _itol(_checksum(pseudo + template[34:54]), 2, 1))
            # update the sequence in Tcp header
            _substitute(template, 38, _itol(tseq, 4, 1))
            return ''.join([chr(c) for c in template])

        opts = dict()
        _dialog = None
        sourceIpAddr = (1, 1, 1, 1)
        destIpAddr = (2, 2, 2, 2)
        if '_i' in kws:
            _dialog = kws['_i'].dialog

            configs = kws['_i'].configs()
            sourceIpAddr = _ensureIpAddr(configs.get(_SOURCE_IP_ADDR, ''),
                                         sourceIpAddr)
            destIpAddr = _ensureIpAddr(configs.get(_DEST_IP_ADDR, ''),
                                       destIpAddr)

        if _dialog is not None:
            resource = _resource
            try:
                mtypes = wx.TheMimeTypesManager.EnumAllFileTypes()

                items = ''
                for m in mtypes:
                    items += '<item>%s</item>' % m

                resource = resource.replace('<content></content>', items)
            except wx.PyAssertionError:
                pass

            opts = _dialog(resource)
            if opts is None:
                return False

        size = len(source)
        # get the HTTP header
        http = opts.get('http_header', _HTTP_HEADER)

        http = re.sub('\r', '', http)
        http = http.strip()
        http += '\x0d\x0a\x0d\x0a'
        http = re.sub('@PACKAGE_SIZE@', str(size), http)
        http = re.sub('@MIME_TYPE@', opts.get('mime_type', 'text/plain'), http)
        if http.find('%DATE%') > 0:
            http = http.replace('%DATE%', time.ctime(0))

        target = kws['_i'].objfmt
        header = target.newFile(data=http)
        jf = target.newFile(afiles=(header, source))

        # get the package size
        psize = min(int(opts.get('package_size', 64000)), 65535)
        output.write(_buildPcapHeader(size + len(http), psize))

        offset = 0
        while offset < len(jf):
            content = jf.read(psize, offset)
            length = len(content)
            output.write(_buildPcapPacket(length))
            output.write(_buildPacketHeader(length, offset,
                                            sourceIpAddr, destIpAddr))
            output.write(content)
            offset += length

        return True


def Service():
    return _Libpcap, 'formatter,generator'
